package it.antonio.ilpizze.db;

import static it.antonio.ilpizze.db.core.Status.CLOSED;
import static it.antonio.ilpizze.db.core.Status.NEED_RESTART;
import static it.antonio.ilpizze.db.core.Status.OPEN;
import static it.antonio.ilpizze.db.core.Status.START;
import static it.antonio.ilpizze.db.core.Status.TRANSACTION;
import it.antonio.ilpizze.db.core.BackupManager;
import it.antonio.ilpizze.db.core.CachesProvider;
import it.antonio.ilpizze.db.core.DbFileSystem;
import it.antonio.ilpizze.db.core.GlobalConfiguration;
import it.antonio.ilpizze.db.core.IndexManager;
import it.antonio.ilpizze.db.core.LockManager;
import it.antonio.ilpizze.db.core.MaintenanceManager;
import it.antonio.ilpizze.db.core.MonitorManager;
import it.antonio.ilpizze.db.core.RecordFileRecords;
import it.antonio.ilpizze.db.core.RecordManager;
import it.antonio.ilpizze.db.core.RecoverManager;
import it.antonio.ilpizze.db.core.SecurityDbManager;
import it.antonio.ilpizze.db.core.Status;
import it.antonio.ilpizze.db.core.StatusManager;
import it.antonio.ilpizze.db.core.TransactionManager;
import it.antonio.ilpizze.db.core.fs.SecurityFile;
import it.antonio.ilpizze.db.core.id.FilePositionId;
import it.antonio.ilpizze.db.exceptions.AutoCommitException;
import it.antonio.ilpizze.db.exceptions.DbInternalException;
import it.antonio.ilpizze.db.io.IOFile;
import it.antonio.ilpizze.db.statistics.Statistics;
import it.antonio.ilpizze.func.Pair;
import it.antonio.ilpizze.func.Predicate;
import it.antonio.ilpizze.func.Value;

import java.io.File;
import java.util.Iterator;

public class DbEngine {

	private DbFileSystem dbFileSystem;

	private RecordManager recordManager;
	
	private TransactionManager txManager;
	
	private CachesProvider caches;
	private SecurityDbManager securityManager;
	
	
	private GlobalConfiguration configuration;
	
	private BackupManager backupManager;
	
	private StatusManager statusManager;
	
	private LockManager lockManager;
	
	private IndexManager indexManager;
	
	private MonitorManager monitorManager;
	
	private MaintenanceManager maintenanceManager;
	
	public DbEngine(File directory) {
		if (!directory.isDirectory()) {
			throw new DbException("Invalid Directory");
		}
		
		//throw new IllegalArgumentException("Guarda le due righe qui sotto che merda che sono");
		statusManager = new StatusManager(START);
		lockManager = new LockManager(statusManager);
		this.dbFileSystem = new DbFileSystem(directory);
		
	}
	
	
	public DbEngine open(){
		
		checkStatus(START);
		
		txManager = new TransactionManager(dbFileSystem);
		
		SecurityFile securityFile = dbFileSystem.openSecurityFile();
		securityFile.init();
		
		securityManager = new SecurityDbManager(securityFile, statusManager, lockManager);
		caches = new CachesProvider(txManager, statusManager, lockManager);
		
		backupManager = new BackupManager(dbFileSystem, statusManager, lockManager);
		
		monitorManager = new MonitorManager();
		monitorManager.registerMeasure().dbSize(new Value<Long>() {
			
			@Override
			public Long value() {
				return dbFileSystem.calculateSize();
			}
		});
		
		configuration = new GlobalConfiguration(txManager, statusManager, lockManager);
		
		
		IOFile recordFile = dbFileSystem.openRecordFile();
		dbFileSystem.lockDb();
		recordManager = new RecordManager(recordFile, txManager);
		
		indexManager = new IndexManager(dbFileSystem, txManager, statusManager, lockManager);
		indexManager.loadIndices();
		
		maintenanceManager = new MaintenanceManager(dbFileSystem, recordManager, indexManager, securityManager, statusManager, lockManager);
		
		// recover
		RecoverManager recoverManager = new RecoverManager(dbFileSystem.openRecoverTransactions());
		recoverManager.tryRecover(recordManager);
	
		statusManager.status(OPEN);
		
		return this;
	}
	
	public Monitor monitor(){
		checkStatus(OPEN, TRANSACTION);
		return monitorManager;
	}
	public Locker locker(){
		checkStatus(OPEN, TRANSACTION);
		return lockManager;
	}
	
	public Caches caches(){
		return caches;
	}
	
	public Globals globals(){
		checkStatus(OPEN);
		return configuration;
	}
	
	public Security security(){
		checkStatus(OPEN, TRANSACTION);
		
		return securityManager;
	}
	
	public Indexes indexes(){
		
		checkStatus(OPEN, TRANSACTION);
		
		return indexManager;
	}
	public Backups backups(){
		checkStatus(OPEN);
		return backupManager;
	}
	
	public Maintenance maintenance() {
		
		checkStatus(OPEN);
		
		return maintenanceManager;
	}
	
	
	public Id save(final Record record) {
		try {
			
			if (txManager.autoCommit()) {
				checkStatus(OPEN);
			} else {
				checkStatus(TRANSACTION);
			}
			monitorManager.start(Statistics.RecordSave);
			
			FilePositionId id = recordManager.allocate(record);
			recordManager.save(id, record);
			
			return id;

		} catch (DbException e) {
			if(txManager.autoCommit() == false) e.needsRoolback(true);
			throw e;
		} catch (Throwable e) {
			DbInternalException dbe = new DbInternalException(e);
			if(txManager.autoCommit() == false) dbe.needsRoolback(true);
			throw dbe;
		} finally {
			monitorManager.stop(Statistics.RecordSave);
		}
	}
	public Id update(Id oldId , Record record) {
		try {
			
			if (txManager.autoCommit()) {
				checkStatus(OPEN);
			} else {
				checkStatus(TRANSACTION);
			}
			monitorManager.start(Statistics.RecordUpdate);
			
			FilePositionId newId = recordManager.allocate(record);
			recordManager.remove((FilePositionId) oldId);
			recordManager.save(newId, record);
			
			return newId;

		} catch (DbException e) {
			if(txManager.autoCommit() == false) e.needsRoolback(true);
			throw e;
		} catch (Throwable e) {
			DbInternalException dbe = new DbInternalException(e);
			if(txManager.autoCommit() == false) dbe.needsRoolback(true);
			throw dbe;
		} finally {
			monitorManager.stop(Statistics.RecordUpdate);
		}
	}
	public Record load(Id id) {
		try {
			checkStatus(OPEN, TRANSACTION);
			monitorManager.start(Statistics.RecordLoad);
			
			return recordManager.read((FilePositionId)id);
		} catch (DbException e) {
			throw e;
		} catch (Throwable e) {
			throw new DbInternalException(e);
		} finally {
			monitorManager.stop(Statistics.RecordLoad);
		}
	}

	public Records load(Predicate<Record> pred) {
		try {
			checkStatus(OPEN, TRANSACTION);
			monitorManager.start(Statistics.RecordLoad);
		
			Iterator<Pair<Id, Record>> it = recordManager.load(pred);
			return new RecordFileRecords(monitorManager, it);	
				
		} catch (DbException e) {
			throw e;
		} catch (Throwable e) {
			throw new DbInternalException(e);
		}
	}
	
	public Record remove(final Id id) {
		try {
			if (txManager.autoCommit()) {
				checkStatus(OPEN);
			} else {
				checkStatus(TRANSACTION);
			}
			
			Record record = recordManager.remove((FilePositionId)id);;
			
			return record;
		} catch (DbException e) {
			if (txManager.autoCommit() == false)
				e.needsRoolback(true);
			throw e;
		} catch (Throwable e) {
			DbInternalException dbe = new DbInternalException(e);
			if (txManager.autoCommit() == false)
				dbe.needsRoolback(true);
			throw dbe;
		} finally {
		}
	}

	public DbEngine beginTx() {
		try{
			checkStatus(OPEN);
			if(txManager.autoCommit()){
				throw new AutoCommitException("Cannot begin Transactions in autocommit mode");
			}
			monitorManager.start(Statistics.Transaction);
		
			try{
				
				txManager.openTransactionLog();
				
				caches.begin();
				
				statusManager.status(TRANSACTION);
			} catch (DbException e) {
				throw e;
			} catch (Throwable t) {
				throw new DbInternalException(t);
			}
		} finally {
		}
		return this;
	}
	
	
	public DbEngine commit(){
		try{
			
			if(configuration != null && txManager.autoCommit()){
				throw new AutoCommitException("Cannot begin Transactions in autocommit mode");
			}
			try{
				checkStatus(TRANSACTION);
				
				txManager.commit();
				txManager.disposeTransactionLog();
				
				caches.commit();
				
				
				statusManager.status(OPEN);
				monitorManager.stop(Statistics.Transaction);
			} catch (DbException e) {
				throw e;
			} catch (Throwable t) {
				throw new DbInternalException(t);
			}
		} finally {
		}
		return this;
	}
	
	public DbEngine rollback(){
		try{
			if(configuration != null && txManager.autoCommit()){
				throw new AutoCommitException("Cannot begin Transactions in autocommit mode");
			}
			checkStatus(TRANSACTION);
			
			try{
				txManager.rollback();
				txManager.disposeTransactionLog();
				
				caches.rollback();
				
				statusManager.status(OPEN);
			} catch (DbException e) {
				throw e;
			} catch (Throwable t) {
				throw new DbInternalException(t);
			}
		} finally {
			monitorManager.stop(Statistics.Transaction);
		}
		return this;
	}

	
	public DbEngine close() {
		try{
			checkStatus(OPEN, NEED_RESTART);
			try{
				
				if(statusManager.status() != NEED_RESTART){
					dbFileSystem.closeAllFiles();
					dbFileSystem.unlockDb();
				}
					
				statusManager.status(CLOSED);
				
			} catch (DbException e) {
				throw e;
			} catch (Throwable t) {
				throw new DbInternalException(t);
			}
		} finally {
		}
		return this;
	}

	private void checkStatus(Status...expected) {
		statusManager.checkStatus(expected);
	}

	
}
